
/***********************************************************************
 * Ship class
 * 
 * Implements a ship artifact
 *
 * Signature:
 *  public static Ship create (String name, Coord position, int energy);
 *  public void install ();
 *  public void destroy ();
 *  public Coord position ();
 *  public String name ();
 *  public void report (String message);
 *  public void changePosition (Coord target);
 *  public boolean isOrbitable ();
 *  public void takeDamage (int damage);
 *  public abstract char display ();
 *  public abstract String toString ();
 *  public int energy ();
 *  public void move (int dx, int dy) {
 ***********************************************************************/

class Ship extends Artifact {

  private int energy;
  private int initialEnergy;

  protected Ship (String n, Coord p, int e) {
    super(n,p);
    this.energy = e;
    this.initialEnergy = e;
  }

  public static Ship create (String n, Coord p, int e) {
    Ship sh = new Ship(n,p,e);
    sh.install();
    return sh;
  }

  public char display () {
    return '^';
  }

  public String toString () {
    return "Ship " + this.name() + " " + this.position().toString();
  }

  public int energy () {
    return this.energy;
  }

  public boolean isNear (Artifact art) {
    return (art.position().within(this.position().move(-1,-1),
				  this.position().move(1,1))
	    && !art.position().equals(this.position()));
  }

  public void orbit (Artifact orb) {
    if (!this.isNear(orb)) {
      report(orb.name() + " too far to orbit");
      return;
    }
    if (orb.isOrbitable()) {
      report("orbiting " + orb.name());
      restoreEnergy();
      return;
    }
    report("cannot orbit around " + orb.name());
  }

  protected void restoreEnergy () {
    report("energy reserves recharged to " + initialEnergy);
    energy = initialEnergy;
  }

  public void takeDamage (int d) {
    if (d<0)
      throw new IllegalArgumentException("Ship.takeDamage()");
    report("taking damage " + d);
    useEnergy(d);
    if (energy==0) {
      report("AAAAARRRGHHHH! *BOOM*");
      destroy();
    }
  }

  public void fire (int dx, int dy) {
    if (energy <= 5) {
      report("insufficient power to fire");
      return;
    }
    Coord c = this.position().move(dx,dy);
    report("firing to " + c);
    useEnergy(5);
    if (!(c.within(Coord.create(0,0),
		   SpaceGame.map().size().move(-1,-1)))) {
      report ("target position out of bounds");
      return;
    }
    Option<Artifact> target = SpaceGame.map().findArtifactByPosition(c);
    if (target.isNone()) {
      report("shot goes wide");
      return;
    }
    report("direct hit on " + target.valOf().name());
    target.valOf().takeDamage(10);
  }

  protected void useEnergy (int used) {
    energy = energy - used;
    if (energy<=0) energy = 0;
    report("energy down to " + energy);
    if (energy==0)  
      report("life-support systems failing");
  }
	
  public void move (int dx, int dy) {
    Coord c = this.position().move(dx,dy);
    int cost = this.position().distanceInt(c);
    if (energy <= cost) {
      report("insufficient power to move as requested");
      return;
    }
    report("attempting to move to " + c);
    if (!(c.within(Coord.create(0,0),
		   SpaceGame.map().size().move(-1,-1)))) {
      report ("target position out of bounds");
      return;
    }
    useEnergy(cost);
    Option<Artifact> target = SpaceGame.map().findArtifactByPosition(c);
    if (target.isNone()) {
      report("moved to " + c);
      changePosition(c);
      return;
    }
    // there's something at the target position
    report("emergency collision avoidance!");
    do {
      Coord shift = Util.randomShift(5);
      c = c.add(shift);
      useEnergy(5);
    } while (!c.within(Coord.create(0,0),
		       SpaceGame.map().size().move(-1,-1)) ||
	     !SpaceGame.map().findArtifactByPosition(c).isNone());
    report("moved to " + c);
    changePosition(c);
  }
  
  public String jsonString() {
    return "{Ship:[" + this.name() + "," + this.position() + "," 
      + this.energy() + "]}";
  }

  public Artifact fromJSONString(String jsonString) {
    String fields[] = JSON.decodeJSONArray(jsonString);
    if(fields.length != 3) {
      throw new RuntimeException(JSON.malformedErrorString("Ship",
							   jsonString));
    }

    try {
      return this.create(fields[0], 
			 Coord.create(0,0).fromJSONString(fields[1]),
			 Integer.parseInt(fields[2]));
    } catch (NumberFormatException nfe) {
      throw new RuntimeException(JSON.malformedErrorString("Ship",
							   jsonString));
    } catch (RuntimeException re) {
      System.err.println("Following error encountered while parsing a"
			 + " Ship json string. (" + jsonString + ")");
      throw re;
    }
  }
}
